<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>10、Symbol——内置的Symbol值</title>
	</head>
	<body>
		<script type="text/javascript">
			//七、内置的Symbol值
			//除了定义自己使用的Symbol值以外，ES6还提供了11个内置的Symbol值，指向语言内部使用的方法。
			
			//1.Symbol.hasInstance
			//对象的Symbol.hasInstance属性，指向一个内部方法。当其他对象使用instanceof运算符，判断是否为该对象的实例时，会调用这个方法。比如，foo instanceof Foo在语言内部，实际调用的是Foo[Symbol.hasInstance](foo)。
			class MyClass{
				[Symbol.hasInstance](foo){
					return foo instanceof Array;
				}
			}
			console.log([1,2,3] instanceof new MyClass()) //true
			//上面代码中，MyClass是一个类，new MyClass()会返回一个实例。该实例的Symbol.hasInstance方法，会在进行instanceof运算时自动调用，判断左侧的运算子是否为Array的实例。
			class Even{
				static [Symbol.hasInstance](obj){
					return Number(obj) % 2 ===0;
				}
			}
			//等同于
			const Even_1 = {
				[Symbol.hasInstance](obj){
					return Number(obj)%2 === 0;
				}
			}
			console.log(1 instanceof Even) //false
			console.log(2 instanceof Even) //true
			console.log(12345 instanceof Even)  //false
			
			//2.Symbol.isConcatSpreadable
			//对象的Symbol.isConcatSpreadable属性等于一个布尔值，表示该对象用于Array.prototype.concat()时，是否可以展开。
			let arr1 = ['c','d'];
			console.log(['a','b'].concat(arr1,'e')) //["a", "b", "c", "d", "e"]
			console.log(arr1[Symbol.isConcatSpreadable]) //undefined
			
			let arr2 = ['c','d'];
			arr2[Symbol.isConcatSpreadable] = false;
			console.log(['a','b'].concat(arr2,'e')) //["a", "b", Array(2), "e"]
			//上面代码说明，数组的默认行为是可以展开，Symbol.isConcatSpreadable默认等于undefined。该属性等于true时，也有展开的效果。
			
			//类似数组的对象正好相反，默认不展开。它的Symbol.isConcatSpreadable属性设为true，才可以展开。
			let obj = {length:2,0:'c',1:'d'};
			console.log(['a','b'].concat(obj,'e')) //["a", "b", Object, "e"]
			obj[Symbol.isConcatSpreadable] = true;
			console.log(['a','b'].concat(obj,'e')) //["a", "b", "c", "d", "e"]
			
			//Symbol.isConcatSpreadable属性也可以定义在类里面。
			class A1 extends Array {
				constructor(args) {
					super(args);
					this[Symbol.isConcatSpreadable] = true;
				}
			}
			class A2 extends Array {
				constructor(args){
					super(args);
				}
				get [Symbol.isConcatSpreadable] (){
					return false;
				}
			}
			let a1 = new A1();
			a1[0] = 3;
			a1[1] = 4;
			let a2 = new A2();
			a2[0] = 5;
			a2[1] = 6;
			console.log([1,2].concat(a1).concat(a2)) //[1, 2, 3, 4, [5,6]]
			//上面代码中，类A1是可展开的，类A2是不可展开的，所以使用concat时有不一样的结果。
			//注意，Symbol.isConcatSpreadable的位置差异，A1是定义在实例上，A2是定义在类本身，效果相同。
			
			
		//3.Symbol.species
		//对象的Symbol.species属性，指向当前对象的构造函数。创造实例时，默认会调用这个方法，即使用这个属性返回的函数当作构造函数，来创造新的实例对象。
		class MyArray extends Array {
			//覆盖父类Array的构造函数
			static get [Symbol.species](){
				return Array;
			}
		}
		//上面代码中，子类MyArray继承了父类Array。创建MyArray的实例对象时，本来会调用它自己的构造函数（本例中被省略了），但是由于定义了Symbol.species属性，所以会使用这个属性返回的的函数，创建MyArray的实例。
		//这个例子也说明，定义Symbol.species属性要采用get读取器。默认的Symbol.species属性等同于下面的写法。
//		static get [Symbol.species](){
//			return this;
//		}
		//下面是一个例子
		class MyArray_1 extends Array {
			static get [Symbol.species](){
				return Array;
			}
		}
		let a = new MyArray_1(1,2,3)
		let mapped = a.map(x=>x*x);
		console.log(mapped instanceof MyArray_1) //false
		console.log(mapped instanceof Array)  //true
		//上面代码中，由于构造函数被替换成了Array。所以，mapped对象不是MyArray的实例，而是Array的实例。
		
		
		//4.Symbol.match
		//对象的Symbol.match属性，指向一个函数。当执行str.match(myObject)时，如果该属性存在，会调用它，返回该方法的返回值。
//		String.prototype.match(regexp)
//		//等同于
//		regexp[Symbol.match](this)
		
		class MyMatcher {
			[Symbol.match](string){
				return 'hello world'.indexOf(string);
			}
		}
		console.log('e'.match(new MyMatcher())) //1
		
		
		//5.Symbol.replace
		//对象的Symbol.replace属性，指向一个方法，当该对象被String.prototype.replace方法调用时，会返回该方法的返回值。
//		String.prototype.replace(searchValue,replaceValue)
//		//等同于
//		searchValue[Symbol.replace](this,replaceValue)
		
		//下面是一个例子
		const x = {};
		x[Symbol.replace] = (...s) => console.log(s);
		'Hello'.replace(x,'World')  //["Hello", "World"]
		//Symbol.replace方法会收到两个参数，第一个参数是replace方法正在作用的对象，上面例子是Hello，第二个参数是替换后的值，上面例子是World。
		
		
		//6.Symbol.search
		//对象的Symbol.search属性，指向一个方法，当该对象被String.prototype.search方法调用时，会返回该方法的返回值。
//		String.prototype.search(RegExp)
//		//等同于
//		RegExp[Symbol.search](this)
		
		class MySearch {
			constructor(value){
				this.value = value;
			}
			[Symbol.search](string){
				return string.indexOf(this.value);
			}
		}
		console.log('foobar'.search(new MySearch('foo')))   //0
		
		
		//7.Symbol.split
		//对象的Symbol.split属性，指向一个方法，当该对象被String.prototype.split方法调用时，会返回该方法的返回值。
//		String.prototype.split(separator,limit)
//		//等同于
//		separator[Symbol.split](this,limit)
		//下面是一个例子
		class MySplitter {
			constructor(value) {
				this.value = value;
			}
			[Symbol.split](string){
				let index = string.indexOf(this.value);
				if(index === -1){
					return string;
				}
				return [
					string.substr(0,index),
					string.substr(index+this.value.length)
				];
			}
		}
		console.log('foobar'.split(new MySplitter('foo'))) //["", "bar"]
		console.log('foobar'.split(new MySplitter('bar'))) //["foo", ""]
		console.log('foobar'.split(new MySplitter('baz'))) //foobar
		//上面方法使用Symbol.split方法，重新定义了字符串对象的split方法的行为
		
		
		//8.Symbol.iterator
		//对象的Symbol.iterator属性，指向该对象的默认遍历器方法
		const myIterable = {};
		myIterable[Symbol.iterator] = function*(){
			yield 1;
			yield 2;
			yield 3;
		};
		console.log([...myIterable]) //[1,2,3]
		//对象进行for...of循环时，会调用Symbol.iterator方法，返回该对象的默认遍历器，详细介绍参见《Iterator和for...of循环》一章。
		class Collection {
			*[Symbol.iterator](){
				let i = 0;
				while(this[i]!==undefined){
					yield this[i];
					++i;
				}
			}
		}
		let myCollection = new Collection();
		myCollection[0] = 1;
		myCollection[1] = 2;
		for(let value of myCollection){
			console.log(value)  //1    2
		}
		
		
		//9.Symbol.toPrimitive
		//对象的Symbol.toPrimitive属性，指向一个方法。该对象被转为原始类型的值时，会调用这个方法，返回该对象对应的原始类型值。
		//Symbol.toPrimitive被调用时，会接受一个字符串参数，表示当前运算的模式，一共有三种模式。
		//a. Number：该场合需要转成数值
		//b. String：该场合需要转成字符串
		//c. Default：该场合可以转成数值，也可以转成字符串
		let obj_1 = {
			[Symbol.toPrimitive](hint) {
				switch (hint) {
					case 'number':
						return 123;
					case 'string':
						return 'str';
					case 'default':
						return 'default'
					default:
						throw new Error();
				}
			}
		}
		
		console.log(2*obj_1)  //246
		console.log(3+obj_1)  //3default
		console.log(obj_1 == 'default') //true
		console.log(String(obj_1)) //str
		
		
		//10.Symbol.toStringTag
		//对象的Symbol.toStringTag属性，指向一个方法。在该对象上面调用Object.prototype.toString方法时，如果这个属性存在，它的返回值会出现在toString方法返回的字符串之中，表示对象的类型。也就是说，这个属性可以用来定制[object Object]或[object Array]中object后面的那个字符串。
		//例一
		console.log(({[Symbol.toStringTag]:'Foo'}.toString())) //[object Foo]
		//例二
		class Collection_1 {
			get [Symbol.toStringTag](){
				return 'xxx';
			}
		}
		let x_1 = new Collection_1();
		console.log(Object.prototype.toString.call(x_1)) //[object xxx]
		
		//ES6新增内置对象的Symbol.toStringTag属性值如下。

//	    JSON[Symbol.toStringTag]：'JSON'
//	    Math[Symbol.toStringTag]：'Math'
//	    Module对象M[Symbol.toStringTag]：'Module'
//	    ArrayBuffer.prototype[Symbol.toStringTag]：'ArrayBuffer'
//	    DataView.prototype[Symbol.toStringTag]：'DataView'
//	    Map.prototype[Symbol.toStringTag]：'Map'
//	    Promise.prototype[Symbol.toStringTag]：'Promise'
//	    Set.prototype[Symbol.toStringTag]：'Set'
//	    %TypedArray%.prototype[Symbol.toStringTag]：'Uint8Array'等
//	    WeakMap.prototype[Symbol.toStringTag]：'WeakMap'
//	    WeakSet.prototype[Symbol.toStringTag]：'WeakSet'
//	    %MapIteratorPrototype%[Symbol.toStringTag]：'Map Iterator'
//	    %SetIteratorPrototype%[Symbol.toStringTag]：'Set Iterator'
//	    %StringIteratorPrototype%[Symbol.toStringTag]：'String Iterator'
//	    Symbol.prototype[Symbol.toStringTag]：'Symbol'
//	    Generator.prototype[Symbol.toStringTag]：'Generator'
//	    GeneratorFunction.prototype[Symbol.toStringTag]：'GeneratorFunction'
		
		
		//11.Symbol.unscopables
		//对象的Symbol.unscopables属性，指向一个对象。该对象指定了使用with关键字时，哪些属性会被with环境排除。
		console.log(Array.prototype[Symbol.unscopables])
		// {
		//   copyWithin: true,
		//   entries: true,
		//   fill: true,
		//   find: true,
		//   findIndex: true,
		//   includes: true,
		//   keys: true
		// }
		console.log(Object.keys(Array.prototype[Symbol.unscopables]))//["copyWithin", "entries", "fill", "find", "findIndex", "includes", "keys"]
		//上面代码说明，数组有7个属性，会被with命令排除。
		//没有unscopables时
		class MyClass_1 {
			foo() {
				return 1;
			}
		}
		var foo = function(){
			return 2;
		};
		
		with(MyClass_1.prototype){
			console.log(foo()) //1
		}
		
		//有 unscopables 时
		class MyClass_2 {
			foo(){
				return 1;
			}
			get [Symbol.unscopables](){
				return {
					foo :true
				}
			}
		}
		var foo_1 = function () {
			return 2;
		}
		with (MyClass.prototype){
			console.log(foo()) //2
		}
		//上面代码通过指定Symbol.unscopables属性，使得with语法块不会在当前作用域寻找foo属性，即foo将指向外层作用域的变量。
		</script>
	</body>
</html>
